﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Collections;

using ComLib;
using ComLib.Configuration;
using ComLib.Extensions;
using ComLib.Entities;
using ComLib.Authentication;
using ComLib.Logging;
using ComLib.Feeds;

using ComLib.Web.Lib.Models;
using ComLib.Web.Lib.Helpers;
using ComLib.Web.Lib.Extensions;
using ComLib.Web.Lib.Results;
using ComLib.Web.Lib.Settings;


namespace ComLib.Web.Lib.Controllers
{
    public class ControllerFactoryHelper
    {
        private static Func<IDictionary> _fetcher = null;
        private static Func<GeneralSettings> _settingsFetcher = null;

        /// <summary>
        /// Initialize the fetcher for the configsource.
        /// </summary>
        /// <param name="configFetcher"></param>
        public static void Init(Func<IDictionary> configFetcher)
        {
            _fetcher = configFetcher;
        }


        /// <summary>
        /// Initialize the fetcher for the configsource.
        /// </summary>
        /// <param name="configFetcher"></param>
        public static void Init(Func<GeneralSettings> settings)
        {
            _settingsFetcher = settings;
        }


        /// <summary>
        /// Get the config source.
        /// </summary>
        /// <returns></returns>
        public static IDictionary GetConf()
        {
            if (_fetcher == null) return new ConfigSource();
            return _fetcher();
        }


        /// <summary>
        /// Get the config source.
        /// </summary>
        /// <returns></returns>
        public static GeneralSettings Settings()
        {
            return _settingsFetcher();
        }
    }



    /// <summary>
    /// Base controller with config settings in the form of a IDictionary.
    /// </summary>
    public class EntityControllerBase : Controller
    {
        protected string _entityName;


        /// <summary>
        /// Default construction.
        /// </summary>
        public EntityControllerBase()
        {
            _entityName = this.GetType().Name.Replace("Controller", "");
            Conf = ControllerFactoryHelper.GetConf();
            Log = Logger.Default;
            Init();
        }


        #region Public Properties
        /// <summary>
        /// ConfigSettings.
        /// </summary>
        public IDictionary Conf { get; set; }


        /// <summary>
        /// The logger.
        /// </summary>
        public ILogMulti Log { get; set; }


        /// <summary>
        /// Name of the entity this is associated with. e.g. Post if "PostController".
        /// </summary>
        public string EntityName { get { return _entityName; } }
        #endregion

        
        #region Life-cycle events
        /// <summary>
        /// Initialize.
        /// </summary>
        protected virtual void Init()
        {
        }
        #endregion


        #region Error Handling
        /// <summary>
        /// Called when an unhandled exception occurs in the action.
        /// </summary>
        /// <param name="filterContext">Information about the current request and action.</param>
        protected override void OnException(ExceptionContext filterContext)
        {
            try
            {
                string url = this.Request.Url.AbsoluteUri;
                string hostname = this.Request.UserHostAddress;
                string message = filterContext.Exception.Message;
                string error = string.Format("Error :{0}, HostName :{1}, Url :{2}", message, hostname, url);
                Log.Error(error, filterContext.Exception);
                base.OnException(filterContext);
            }
            catch (Exception) { }
        }
        #endregion
    }



    /// <summary>
    /// Base class for controllers.
    /// </summary>
    public class EntityControllerBase<T> : EntityControllerBase where T : class, IEntity, new()
    {
        protected string PageLocationForCreate = "Pages/Create";
        protected string PageLocationForEdit   = "Pages/Edit";
        protected string PageLocationForList = "Pages/List";
        protected string PageLocationForManage = "Pages/Manage";
        protected string PageLocationForDetails = "Pages/Details";
        protected string PageLocationForDeleted = "Pages/Deleted";


        #region Mvc Controller methods
        /// <summary>
        /// Show the post with an seo-optimized title.
        /// e.g. my-first-how-to-472 where 472 is the post id.
        /// </summary>
        /// <param name="title"></param>
        /// <returns></returns>
        public ActionResult Show(string title)
        {
            string strId = title.Substring(title.LastIndexOf("-") + 1);
            int id = Convert.ToInt32(strId);
            IEntityService<T> service = EntityRegistration.GetService<T>();
            T entity = service.Get(id);
            return InternalDetails(entity);
        }


        /// <summary>
        /// Create a new entity.
        /// </summary>
        /// <returns></returns>
        // [Authorize]
        public virtual ActionResult Create()
        {
            return InternalCreate();
        }


        /// <summary>
        /// Edit the entity referenced by id.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [Authorize]        
        public virtual ActionResult Edit(int id)
        {
            IEntityService<T> service = EntityRegistration.GetService<T>();
            T entity = service.Get(id);

            if (entity == null) return View("Pages/NotFound");

            var model = new EntityFormViewModel<T>();
            BuildViewModel(model, entity, "ModelForm", true);
            return View(PageLocationForEdit, model);
        }


        /// <summary>
        /// Edit the entity referenced by id.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        [AcceptVerbs(HttpVerbs.Post), Authorize]
        public virtual ActionResult Edit(int id, FormCollection collection)
        {
            string successUrl = Conf.GetOrDefault<string>("CreateSuccessUrl", "Details");
            return Edit(id, successUrl);
        }


        /// <summary>
        /// Show the details of the entity.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual ActionResult Details(int id)
        {
            IEntityService<T> service = EntityRegistration.GetService<T>();
            T entity = service.Get(id);
            return InternalDetails(entity);
        }


        /// <summary>
        /// Delete the entity with the id.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [Authorize]
        public virtual ActionResult Delete(int id)
        {
            return InternalDelete(id, string.Empty);
        }


        /// <summary>
        /// Delete the entity with confirmation.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="confirmButton"></param>
        /// <returns></returns>
        [AcceptVerbs(HttpVerbs.Post), Authorize]
        public virtual ActionResult Delete(int id, string confirmButton)
        {
            return InternalDelete(id, confirmButton);
        }


        /// <summary>
        /// Get index of entities ( based on page ).
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>   
        public virtual ActionResult Index(int? page)
        {
            int pageSize = Conf.Get<int>(_entityName, "index", 15);
            var viewModel = InternalIndex(page, false, (svc, pageIndex) => svc.Get(pageIndex, pageSize));
            return View(PageLocationForList, viewModel);
        }


        /// <summary>
        /// Get index of entities ( based on page ).
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>   
        public virtual ActionResult IndexManage(int? page)
        {
            int pageSize = Conf.Get<int>(_entityName, "index", 15);
            var viewModel = InternalIndex(page, true, (svc, pageIndex) => svc.Get(pageIndex, pageSize));
            viewModel.UrlIndex = viewModel.UrlManage;
            return View(PageLocationForList, viewModel);
        }


        /// <summary>
        /// Gets paged index of entities that can be managed(edited/deleted).
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        [Authorize]
        public virtual ActionResult Manage(int? page)
        {            
            int pageSize = Conf.Get<int>(_entityName, "manage", 15);
            EntityListViewModel<T> viewModel = null;

            // Allow manage to view all posts.
            if (Auth.IsAdmin()) 
                viewModel = InternalIndex(page, true, (svc, pageIndex) => svc.Get(pageIndex, pageSize));
            else 
                viewModel = InternalIndex(page, true, (svc, pageIndex) => svc.FindByUser(Auth.UserShortName, pageIndex, pageSize));

            viewModel.UrlIndex = viewModel.UrlManage;
            return View(PageLocationForManage, viewModel);
        }


        /// <summary>
        /// Edit the entity referenced by id.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        [AcceptVerbs(HttpVerbs.Post), Authorize]
        public virtual ActionResult Manage(FormCollection formData)
        {
            IEntityService<T> service = EntityRegistration.GetService<T>();
            IActionContext ctx = EntityRegistration.GetContext<T>();
            ctx.UserName = Auth.UserShortName;

            int page = GetPage("pagenum");
            int pageSize = Conf.Get<int>(_entityName, "manage", 15);
            var viewModel = InternalIndex(page, true, (svc, pageIndex) => svc.FindByUser(Auth.UserShortName, pageIndex, pageSize));
            return View(PageLocationForManage, viewModel);
        }
        #endregion


        #region Rss
        /// <summary>
        /// RSSs this instance.
        /// </summary>
        /// <returns></returns>
        public ActionResult Rss()
        {
            return Feed("rss");
        }


        /// <summary>
        /// Atoms this instance.
        /// </summary>
        /// <returns></returns>
        public ActionResult Atom()
        {
            return Feed("atom");
        }


        protected ActionResult Feed(string format)
        {
            IEntityService<T> service = EntityRegistration.GetService<T>();
            IList<IPublishable> entries = service.GetRecentAs<IPublishable>(1, 20);
            Uri uri = this.HttpContext.Request.Url;
            //http://localhost:49739/Post/rss/

            int secondIndex = uri.AbsoluteUri.IndexOf("/", 7);
            string domain = uri.AbsoluteUri.Substring(0, secondIndex);
            if (domain.Contains("localhost"))
                domain = "http://localhost.com";

            var settings = ControllerFactoryHelper.Settings();
            var feed = FeedBuilder.Build(settings.Author, settings.Title, settings.Description, domain, entries);
            return new FeedActionResult() { Feed = feed, Format = format };
        }
        #endregion


        #region Utility Methods
        /// <summary>
        /// Performs an entity create/edit action.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity">The entity being persisted.</param>
        /// <param name="executor">The lambda to persist the entity.</param>
        /// <param name="successUrlId">Url identitfier e.g. "Details"</param>
        /// <returns></returns>
        protected virtual ActionResult Create(T entity, string successUrlId)
        {
            ActionResult result = null;
            try
            {
                IEntityService<T> service = EntityRegistration.GetService<T>();

                // Save the entity.                
                service.Create(entity);
                if (entity.Errors.IsValid)
                {
                    result = RedirectToAction(successUrlId, new { id = entity.Id });
                }
                else
                {
                    result = BuildViewModelFormWithErrors(entity, PageLocationForCreate, null);
                }
            }
            catch
            {
                result = BuildViewModelFormWithErrors(entity, PageLocationForCreate, "Unexpected error while editing " + typeof(T).Name);
            }
            return result;
        }


        /// <summary>
        /// Performs an entity create and returns a view thanking the user for their input.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity">The entity being persisted.</param>
        /// <param name="executor">The lambda to persist the entity.</param>
        /// <param name="successUrlId">Url identitfier e.g. "Details"</param>
        /// <returns></returns>
        protected virtual ActionResult CreateThankYou(T entity, object messageModel)
        {
            ActionResult result = null;
            try
            {
                IEntityService<T> service = EntityRegistration.GetService<T>();

                // Save the entity.                
                service.Create(entity);
                result = View("Pages/Thankyou", messageModel);
            }
            catch
            {
                ModelState.AddModelError("", "Unexpected error occurred saving " + typeof(T).Name);
                result = View(PageLocationForCreate, entity);
            }
            return result;
        }


        /// <summary>
        /// Edit the entity by passing in the form collection so it maps the 
        /// html fields to the entity properites.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id"></param>
        /// <param name="collection"></param>
        /// <param name="fetcher"></param>
        /// <returns></returns>
        protected virtual ActionResult Edit(int id, string successUrlId)
        {
            T entity = default(T);
            ActionResult result = null;
            try
            {
                IEntityService<T> service = EntityRegistration.GetService<T>();
            
                entity = service.Get(id);
                UpdateModel(entity);
                
                // Persist the edit                
                service.Update(entity);
                if (entity.Errors.IsValid)
                {
                    result = RedirectToAction(successUrlId, new { id = entity.Id });
                }
                else
                {
                    result = BuildViewModelFormWithErrors(entity, PageLocationForEdit, null);
                }
            }
            catch
            {
                result = BuildViewModelFormWithErrors(entity, PageLocationForEdit, "Unexpected error while editing " + typeof(T).Name);
            }
            return result;
        }


        /// <summary>
        /// Show the details of the entity.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        protected virtual ActionResult InternalDetails(T entity)
        {
            var model = new EntityDetailsViewModel<T>();
            BuildViewModel(model, entity, "ModelDetails", false);
            return View(PageLocationForDetails, model);
        }


        /// <summary>
        /// Provides a template for getting paged items.
        /// </summary>
        /// <param name="page"></param>
        /// <param name="allowEditDelete"></param>
        /// <param name="executor"></param>
        /// <returns></returns>
        protected virtual EntityListViewModel<T> InternalIndex(int? page, bool allowEditDelete, Func<IEntityService<T>, int, PagedList<T>> executor)
        {
            IEntityService<T> service = EntityRegistration.GetService<T>();
            IActionContext ctx = EntityRegistration.GetContext<T>();
            int pageIndex = page == null ? 1 : (int)page;
            PagedList<T> result = executor(service, pageIndex);
            var viewModel = new EntityListViewModel<T>();
            BuildViewModel(viewModel, null, "ModelList", false);
            viewModel.Items = result;            
            viewModel.PageIndex = result.PageIndex;
            viewModel.TotalPages = result.TotalPages;
            viewModel.ShowEditDelete = allowEditDelete;
            return viewModel;
        }


        /// <summary>
        /// Create a new entity.
        /// </summary>
        /// <returns></returns>
        protected virtual ActionResult InternalCreate()
        {
            var model = new EntityFormViewModel<T>();
            BuildViewModel(model, new T(), "ModelForm", true);
            return View(PageLocationForCreate, model);
        }


        /// <summary>
        /// Delete the entity.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="confirmButton"></param>
        /// <returns></returns>
        protected virtual ActionResult InternalDelete(int id, string confirmButton)
        {
            IEntityService<T> service = EntityRegistration.GetService<T>();
            IActionContext ctx = EntityRegistration.GetContext<T>(id);
            
            // Get the entity.
            T entity = service.Get(ctx);

            // If not found.
            if (entity == null) return View("Pages/NotFound");

            try
            {
                string modelName = typeof(T).Name;
                string url = "/" + modelName + "/manage";
                string message = modelName + " has been deleted";

                EntityDeletionViewModel deleteModel = new EntityDeletionViewModel(){ Header = modelName + " deleted", 
                    ModelName = modelName, IndexUrlAbsolute = url, Message = message };

                // Try deleting.
                ctx.Item = entity;
                service.Delete(ctx);
                return View(PageLocationForDeleted, deleteModel);
            }
            catch (Exception ex)
            {
                Logger.Error(null, ex);
                return View("Error saving");
            }
        }

        
        /// <summary>
        /// Get the current page used for the index and manage page views.
        /// </summary>
        /// <param name="pageParamName"></param>
        /// <returns></returns>
        protected virtual int GetPage(string pageParamName)
        {
            string pageStr = this.Request.QueryString[pageParamName];
            int pageIndex = 1;
            if (!string.IsNullOrEmpty(pageStr))
            {
                int.TryParse(pageStr, out pageIndex);
            }
            return pageIndex;
        }


        /// <summary>
        /// Builds the view model.
        /// </summary>
        /// <param name="view">The view.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="controlPath">The control path.</param>
        /// <param name="useForm">if set to <c>true</c> [use form].</param>
        protected virtual void BuildViewModel(EntityBaseViewModel view, T entity, string controlPath, bool useForm)
        {
            ViewHelper.BuildViewModel<T>(view, entity, controlPath, useForm, this.Conf);
        }


        /// <summary>
        /// Builds the view model by adding the existing errors in the entity to the model state and return the pagelocation
        /// specified.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="pageLocation"></param>
        /// <returns></returns>
        protected virtual ActionResult BuildViewModelFormWithErrors(T entity, string pageLocation, string additionalError)
        {
            // Add the errors.
            // TODO: Do not know why "id" is being added to model state
            if (ModelState["Id"] != null) ModelState.Remove("Id");
            if(!entity.Errors.IsValid) ModelState.AddErrors(entity);
            if (!string.IsNullOrEmpty(additionalError)) ModelState.AddModelError("", additionalError);

            var model = new EntityFormViewModel<T>();
            BuildViewModel(model, entity, "ModelForm", true);
            ActionResult result = View(pageLocation, model);
            return result;
        }
        #endregion
    }
}
